#include "Classifier.h"
#include <iostream>
#include <stdlib.h>
#include <math.h>
#include <vector>
#include <fstream>


using namespace std;

Classifier::Classifier(int num_f){
    num_features = num_f;
    weights = new double[num_features];
    //weights[0] = -5;
    for(int i = 0; i < num_features; i++)
        weights[i] = 0;
}
  
Classifier::~Classifier(){
    delete[] weights;
}

bool Classifier::writeWeightsToFile(string file){
  ofstream out(file.c_str());
  if(!out){
    printf("Error opening the file!\n");
    return false;
  }
  out << num_features;
  for(int i=0; i<num_features; i++){
     out << " " << weights[i];
  }
  out << endl;
  return true;
}

bool Classifier::readWeightsFromFile(string file){
  ifstream in(file.c_str());
  if(!in.is_open()){
     printf("Error opening file!\n");
     return false;
  }
  float numfeat;
  string line;
  getline(in, line);
  int index = line.find(" ");
  numfeat = atof((line.substr(0,index)).c_str());
  if (numfeat != num_features){
    printf("Error : the weights file has the wrong number of weights!");
    return false;
  }
  line = line.substr(index+1);
  for (int i=0; i<num_features; i++){
     index = line.find(" ");
     weights[i] = atof((line.substr(0,index)).c_str());
     line = line.substr(index+1);
  }
  printf("Weights loaded from file.\n");
  return true;
  
}

// reads data file and stores data to vector<Datum>
bool Classifier::readFile(string file, vector<Datum>* training_data, int num_examples){
    string line;
    ifstream paths;
    paths.open(file.c_str());
    if(!paths.is_open()){
      printf("Error opening file \n");
      return false;
    }
     
    getline(paths,line); 
    int numfeat = atoi(line.c_str());
    printf("Num features : %d\n", numfeat);
    if(numfeat != num_features){
        printf("Error : Wrong number of features in data file!\n");
        return false;
    } 

    vector<Datum> data(num_examples);
    int i,j, index;
    for (j=0; j<num_examples; j++){  //maybe make into a while(!paths.isEmpty())
      getline(paths,line);
      //printf("line %d : %s\n", j, line.c_str());
      Datum d;
      d.num_features = num_features;
      d.features = new double[num_features];
      //printf("features #%d: ",j);
      for (i=0; i<num_features; i++){
          index = line.find(" ");
          //printf("index of space : %d\n",index);
          d.features[i] = atof((line.substr(0,index)).c_str());
          //printf(" %f",d.features[i]);
          line = line.substr(index+1);
      }
      //printf("\n");
	//getchar();
      d.output = atof(line.c_str());
      data[j]=d;
      //printf("Output #%d: %f\n",j,d.output);
    }
    paths.close();
    
    //printf("File said : %f %f %f \n",data[0].features[0], data[0].features[1],data[0].features[2]);
    *training_data = data;
    printf("training data read\n");
    return true;
}


// calculates h_theta(x), using the current weights
double Classifier::h(Datum d){
  double sum = weights[0];
  for (int i=1; i<num_features; i++){
     sum += weights[i]*d.features[i];
  }
  double y = 1/(1 + exp(-1*sum) );
 // printf("y = %f ; sum = %f\n",y,sum);
  return y;
}

// returns 1 if a step is unfeasible, 0 if feasible
int Classifier::getClassOf(Datum d){
  double y = h(d);
  if (y<0.9)
    return 0;
  return 1;
}

void Classifier::printWeights(){
    printf("[");
    for(int i = 0; i < num_features; i++){
      printf("%f ",weights[i]);
    }
    printf("]\n");
}

// training function : sets the weights using a gradient descent algorithm
void Classifier::train(vector<Datum> data){
   printf("Training...\n");
   if (data[0].num_features != num_features){ 
      printf("Error : wrong number of features in training examples\n");
      return;
   }
   int m = data.size(); // number of examples
   printf("m= %d\n",m);
   int n = num_features; 
   double cv_error_2 = 1; // convergence error, squared (= squared enclidian distance between a set of weights and the next)
   double sum = 0;
   int i,j;
   int num_training_iter = 0;
   double alpha = .01; // training rate

   weights[1]=50; // initializing

   while (cv_error_2 > alpha*0.001){
      if (num_training_iter > 100000){ // stop if too many iterations
          printf("Error : %d iterations. Stopping training algorithm.\n", num_training_iter);
          return;
      }
      // compute the new weights
      cv_error_2 = 0;
      double* newWeights = new double[n];
      for (j=0; j<n; j++){
         sum = 0;
         for (i=0; i<m; i++){
            sum += (h(data[i]) - data[i].output)*data[i].features[j]; 
         }
         newWeights[j] = weights[j] - alpha*sum;
         cv_error_2 += pow(newWeights[j] - weights[j], 2);
      }
      weights = newWeights;
      num_training_iter ++;
   }
   printf("Trained, with %d iterations.\n", num_training_iter);
   printWeights();
   return;
}


// computes the training or generalisation error.
double Classifier::computeError(vector<Datum>* v, int num_examples){
   if(num_examples != (*v).size()){
      printf("Error! not the right number of examples.\n");
      return 0;
   }
   double sum=0;
   for(int i=0; i<num_examples; i++){
       sum += pow( h((*v)[i]) - (*v)[i].output ,2);
   }
   return sum/num_examples;
}


